import numpy as np


class Element(object):
    def __init__(self, *children):
        self.children = children


class Node(Element):

    def __init__(self, name=None, anchors=None, label=None, content=None):
        super().__init__()
        self.name = name
        self.anchors = anchors
        self.label = label
        if content is not None:
            self.content = content
        else:
            self.content = ""

    def __call__(self, atr=None):
        if atr is None:
            anchor = f"({self.label})"
        else:
            anchor = f"({self.label}.{atr})"
        return Coordinate(anchor)

    def __repr__(self):
        if self.label is not None:
            label = f" ({self.label}) "
        else:
            label = " "
        if self.name:
            name = f"[{self.name}]"
        else:
            name = ""

        return f"node{name}{label}{{{self.content}}}"

    def __getattr__(self, item):
        if item == "in1" or item == "n":
            item = "-"
        elif item == "in2" or item == "p":
            item = "+"
        return self(item)


class Chain(Element):
    def __init__(self, name, *children):
        super().__init__(*children)
        self.name = f"to[{name}]"

    def __repr__(self):
        return self.name


class Coordinate(object):

    def __init__(self, loc, deviation=np.array([0, 0])):
        if isinstance(loc, str) and loc[0] != "(":
            self.loc = f"({loc})"
        else:
            self.loc = loc
        self.deviation = deviation

    def __repr__(self):
        if self.loc is None:
            return ""
        elif isinstance(self.loc, str):
            if (self.deviation != 0).any():
                return f"{self.loc}++{tuple(self.deviation)}"
            else:
                return f"{self.loc}"
        else:
            return f"{tuple(self.loc + self.deviation)}"

    def __str__(self):
        return self.__repr__()

    def __add__(self, other):
        return Coordinate(self.loc, self.deviation + other)

    def __radd__(self, other):
        return Coordinate(self.loc, self.deviation + other)

    def __sub__(self, other):
        return Coordinate(self.loc, self.deviation - other)

    def __rsub__(self, other):
        return Coordinate(self.loc, other - self.deviation)


class Path(Node):
    escape = {
        'left': "++(-2, 0)",
        'right': "++(2, 0)",
        'up': "++(0, 2)",
        'down': "++(0, -2)",
    }

    def __init__(self, *elements, name="temp", start=Coordinate([0, 0]), need_end=True, option="--"):
        super().__init__()
        self.elements = list(elements)
        self.name = name
        self.start = start
        self.end = need_end
        self.option = option

    def __repr__(self):
        path = ""
        if self.start:
            path += str(self.start)
        for e in self.elements:
            if e in self.escape:
                e = self.escape[e]
            path += f" {e}"
        if self.end:
            self.end = f"({self.name}_end)"
            path += f" coordinate{self.end}"

        # if isinstance(loc2, str) or isinstance(loc2, Node):
        #     self.path = f"{loc1} coordinate(temp) {line} (temp {option} {loc2[1:-1]})--{loc2}"
        # else:
        #     if option[0] == "-":
        #         self.path = str(loc1) + line + str((loc1[0], loc2[1])) + "--" + str(loc2)
        #     else:
        #         pass
        return path

    def add(self, element, direction=None, to_loc=None):
        if not isinstance(element, Element):
            element = Element(element)
        if direction is not None:
            self.elements.append(direction)
            self.elements.append(element)
        elif to_loc is not None:
            to_loc = str(to_loc)
            self.elements.append("coordinate(temp)")
            self.elements.append(element)
            self.elements.append(f"(temp -| {to_loc[1: -1]})--{to_loc}")
        else:
            self.elements.append(element)


class Circuit(object):

    def __init__(self, *paths, name='temp', ports=()):
        for port in ports:
            exec(f"self.{port} = f'{self.__class__.__name__}-{name}-{port}'")
        self.paths = list(paths)

    def __repr__(self):
        out = ""
        for p in self.paths:
            out += f"\\draw {p};\n"
        return out

    def add(self, obj):
        if isinstance(obj, Path):
            self.paths.append(obj)
        else:
            self.paths[-1].add(obj)

    def add_path(self, *args, path=None, **kwargs):
        if path is None:
            path = Path(*args, **kwargs)
        self.paths.append(path)
        return path

    def add_element(self, *args, **kwargs):
        self.paths[-1].add(*args, **kwargs)